/* Copyright 2010 Michael Wenders, michaelwenders <at> gmx <dot> de

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "lcm.h"
#include <string>
#include <sstream>
#include <fstream>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <mysql/mysql.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
using namespace std;

namespace lcm
{
	bool doesFileExist(const string dir)
	{
		struct stat stat_buf;

		if(stat( dir.c_str(),&stat_buf) == 0)
		{
			// delete stat_buf;
			return true;
		}
		else
		{
			// delete stat_buf;
			return false;
		}
	}

	string stringToBash(string command)
	{
		FILE *fpipe = popen(command.c_str(),"r");;
		string output = "";

		char c = '\0';
		while (c != EOF)
		{
			c = fgetc (fpipe);
			if((int)c != -1)
				output += c;
		}

		//check if there is an output
		if(output.size() > 0)
		{
			//mostly last char is an unwanted newline, delete it
			string tmpOutput = "";
			if(output.at(output.length()-1) == (int)10)
			{
				for(unsigned int i = 0; i < output.length()-1; i++)
					tmpOutput += output.at(i);
				output = tmpOutput;
			}
		}
		pclose(fpipe);
		return output;
	}

	string eraseComments(string line)
	{
		string without_comments = "";
		int unsigned iMax = line.find_first_of('#');
		if( iMax == string::npos)
		{//nothing to erase
			return line;
		}
		else
		{//first comment-char at iMax
			for(int unsigned i = 0; i < iMax; i++)
			{
				without_comments += line[i];
			}
		}

		return without_comments;
	}

	string eraseSpaces(string line)
	{
		string dummy = "";
		for(unsigned int i = 0; i < line.length(); i++)
		{
			if(line[i] != ' ')
				dummy += line[i];
		}
		return dummy;
	}

	string copyUntil(string line, char c)
	{
		string cut = "";
		//find c
		unsigned int startPos = line.find_first_of(c);
		if(startPos == string::npos)
		{
			//This time this is not an error, this just means that its the last value
			for(unsigned int i = 0; i < line.length(); i++)
				cut += line.at(i);
		}
		else
		{
			//copy into string
			for(unsigned int i = 0; i < startPos; i++)
				cut += line.at(i);
		}
		return cut;
	}

	string removeUntil(string line, char c)
	{
		string remove = "";
		unsigned int startPos = line.find_first_of(c);
		if(startPos == string::npos)
		{
			//no more c found, return nothing
			return "";
		}
		else
		{
			for(unsigned int i = startPos+1; i < line.length(); i++)
				remove += line.at(i);
		}

		return remove;
	}

	string lineToLowerCase(string line)
	{
		string lowerCase = "";
		for(unsigned int i = 0; i < line.length(); i++)
		{
			if((int)line.at(i) >= 65 && (int)line.at(i) <= 90)
			{ // A-Z
				lowerCase += (char) ((int)line.at(i) + 32);
			}
			else if((int)line.at(i) == 142) // Ä //FIXME: Umlaute doesn't work yet
				lowerCase += (char) ((int)line.at(i) - 10);
			else if((int)line.at(i) == 153) // Ö
				lowerCase += (char) ((int)line.at(i) - 5);
			else if((int)line.at(i) == 154) // Ü
				lowerCase += (char) ((int)line.at(i) - 25);
			else
				lowerCase += line.at(i);
		}
		return lowerCase;
	}

	string eraseChars(string line,char c)
	{
		string dummy = "";
		for(unsigned int i = 0; i < line.length(); i++)
		{
			if(line[i] != c)
				dummy += line[i];
		}
		return dummy;
	}

	string getMetaOption(string line)
	{
		//meta-options is the first word in a string like this: server::ip = 127.0.0.1 ("server" is the meta-option)
		string meta_option = "";

		//at first: is there a ':' in the string?
		if(line.find_first_of(':') == string::npos)
		{
			//FIXME last error
			return "";
		}

		//first char cannot be a ':'
		if(line.at(0) == ':')
			return ""; //FIXME: last error

		//upper functions had to make sure that no spaces are in the line
		for(unsigned int i = 1; i < line.length(); i++)
		{
			//search the first colon
			if(line.at(i) == ':')
			{//got the first colon,searching for the second right behind the first
				if(line.at(i+1) != ':')
				{//this is not allowed, so breaking here
					break;
				}
				else
				{//found the second colon, copy the first word into 'meta-option'
					for(unsigned int j = 0; j < i; j++)
						meta_option += line[j];
					break;
				}
			}
		}
		return meta_option;
	}

	string getParameter(string line)
	{
		string parameter = "";

		//at first: is there a "="?
		if(line.find_first_of('=') == string::npos)
		{
			//FIXME last error
			return "";
		}
		//trying to find the parameter right behind the double colons

		//as in getMetaOption, the upper functions had to make sure that no spaces are left in this line, so that the parameter is followed by a "="
		unsigned int startPos = line.find_first_of(':');
		if(startPos == string::npos)
		{//something went totally wrong, this is not the correct string
			//FIXME last error
			return "";
		}
		else
		{//searching the double colon
			if(line.at(startPos+1) != ':')
			{//this is not a correct string
				//FIXME last error
			}
			else
			{//double-colon found, start copying until first "="
				for(unsigned int i = startPos+2; i < line.length(); i++)
				{
					if(line[i] != '=')
						parameter += line[i];
					else
						break;
				}
			}
		}

		return parameter;
	}

	string getValue(string line)
	{
		string value = "";

		//there is no need to check if a character is wrong, missing etc, because the value can be whatever is wanted sometimes

		//again no spaces are in the string, so we can go on

		for(unsigned int i = line.find_first_of('=')+1; i < line.length(); i++)
		{
			value += line[i];
		}

		return value;
	}

	int stringToInt(string text)
	{
		stringstream ss;
		ss.str(text);
		int number = 0;
		ss >> number;
		return number;
	}

	string intToString(int number)
	{
		stringstream ss;
		string text;

		ss << number;
		ss >> text;
		return text;

	}

	bool amIroot()
	{
		string user = stringToBash("whoami");
		if(user == "root" || user == "lcm")
			return true;
		else
			return false;
	}

	string getMD5(string file)
	{
		string bash = "md5sum "; bash += file; bash += "|cut -d' ' -f1";

		return stringToBash(bash);
	}

	string getDateTime()
	{
		string dateTime = "";
		time_t stamp = time(0);
		tm *now = localtime(&stamp);

		dateTime += lcm::intToString(now->tm_year+1900);
		dateTime += ".";
		dateTime += lcm::intToString(now->tm_mon+1);
		dateTime += ".";
		dateTime += lcm::intToString(now->tm_mday);
		dateTime += " ";
		dateTime += lcm::intToString(now->tm_hour);
		dateTime += ":";
		dateTime += lcm::intToString(now->tm_min);
		dateTime += ":";
		dateTime += lcm::intToString(now->tm_sec);

		return dateTime;
	}

	int readSocket(int socket,string &text)
	{
		char answer[64] = {NULL};// = (char*)malloc(64);
		int retVal = read(socket,answer,64);
		if(retVal == -1 || retVal == 0)
		{
			return lcm::error::NET_COULD_NOT_READ;
		}
		else
		{
			text = answer;
			return 0;
		}
	}

	int writeSocket(int socket, string text)
	{
		if(write(socket,text.c_str(),text.length()) == -1)
		{
			return lcm::error::NET_COULD_NOT_WRITE;
		}
		else
			return 0;
	}

	lcm::log::Logger::Logger(string logfile, int loglevel, int verbosity)
	{
		this->logfile = logfile;
		this->loglevel = loglevel;
		this->verbosity = verbosity;
	}

	void lcm::log::Logger::setLogLevel(int loglevel)
	{
		this->loglevel = loglevel;
	}

	void lcm::log::Logger::setVerbosity(int verbosity)
	{
		this->verbosity = verbosity;
	}

	int lcm::log::Logger::write(string text, int level)
	{ /* verbosity = preconfigured level for output;e.g. verbosity: WARNING, level: DEBUG => do not write*/

		// 1st write to stdout/stderr, 2nd write to logfile

		//check verbosity, maybe put this in setVerbosity? //FIXME
		if(this->verbosity >= lcm::log::LOGGER_DEBUG && this->verbosity <= lcm::log::LOGGER_NONE)
		{
			if(level >= this->verbosity)
			{ /* level is high enough to write it  */
				switch(level)
				{
				case lcm::log::LOGGER_DEBUG:
					cout << "Debug: " << flush;
					break;
				case lcm::log::LOGGER_INFO:
					cout << "Info: " << flush;
					break;
				case lcm::log::LOGGER_WARNING:
					cout << "Warning: " << flush;
					break;
				case lcm::log::LOGGER_ERROR:
					cerr << "Error: " << flush;
					break;
				case lcm::log::LOGGER_CRITICAL:
					cerr << "Critical: " << flush;
					break;
				case lcm::log::LOGGER_PANIC:
					cerr << "Panic: " << flush;
					break;
				case lcm::log::LOGGER_NONE:
					//do nothing
					break;
				}
				if(level >= lcm::log::LOGGER_ERROR && level < lcm::log::LOGGER_NONE)
				{
					cerr << text << endl;
				}
				else if (level < lcm::log::LOGGER_ERROR && level >= lcm::log::LOGGER_DEBUG)
				{
					cout << text << endl;
				}
				else //do nothing
					;
			} // level high enough?

		} //verbosity ok?

		//check loglevel, maybe put this in setLogLevel? //FIXME
		if(this->loglevel >= lcm::log::LOGGER_DEBUG && this->loglevel <= lcm::log::LOGGER_NONE)
		{
			if(level >= this->loglevel)
			{ /* level is high enough to write it  */
				//open file
				fstream file; file.open(this->logfile.c_str(), ios::out | ios::app);
				time_t rawtime = time(0);
				tm *now = localtime(&rawtime);
				file << "["
					 <<	now->tm_year+1900 << "/"
					 << now->tm_mon+1 << "/"
					 << now->tm_mday << " "
					 << now->tm_hour << ":"
					 << now->tm_min << ":"
					 << now->tm_sec << "] " << flush;

				switch(level)
				{
				case lcm::log::LOGGER_DEBUG:
					 file << "Debug: " << flush;
					break;
				case lcm::log::LOGGER_INFO:
					file << "Info: " << flush;
					break;
				case lcm::log::LOGGER_WARNING:
					file << "Warning: " << flush;
					break;
				case lcm::log::LOGGER_ERROR:
					file << "Error: " << flush;
					break;
				case lcm::log::LOGGER_CRITICAL:
					file << "Critical: " << flush;
					break;
				case lcm::log::LOGGER_PANIC:
					file << "Panic: " << flush;
					break;
				case lcm::log::LOGGER_NONE:
					//do nothing
					break;
				}
				if(level >= lcm::log::LOGGER_ERROR && level < lcm::log::LOGGER_NONE)
				{
					file << text << endl;
				}
				else if (level < lcm::log::LOGGER_ERROR && level >= lcm::log::LOGGER_DEBUG)
				{
					file << text << endl;
				}
				else //do nothing
					;
				file.close();
			} // level high enough?

		} //verbosity ok?

		//FIXME: not ok option
		return 0;
	}

	namespace ssl
	{
		RSA* initSSLPubKey(string keyFile)
		{
			RSA *rsa = NULL;
			FILE *keyfile = fopen(keyFile.c_str(), "r");
			if (!keyfile)
				return (RSA*)NULL;

			rsa = PEM_read_RSAPublicKey(keyfile, NULL, NULL, NULL);

			return rsa;
		}

		RSA* initSSLPriKey(string keyFile)
		{
			RSA *rsa = NULL;
			FILE *keyfile = fopen(keyFile.c_str(), "r");
			if (!keyfile)
				return (RSA*)NULL;

			rsa = PEM_read_RSAPrivateKey(keyfile, NULL, NULL, NULL);

			return rsa;
		}

		int readSSLSocket(int socket,string &text, RSA* rsa)
		{
			cout << "In readSSLSocket" << endl;
			char answer[500] = {NULL};// = (char*)malloc(64);
			int retVal = read(socket,answer,500);
			if(retVal == -1 || retVal == 0)
			{
				return lcm::error::NET_COULD_NOT_READ;
			}
			else
			{
				unsigned char* decrypted = (unsigned char *) malloc(retVal+1);

				//decrypt answer
				if (RSA_private_decrypt(retVal+1, (unsigned char*)answer, decrypted, rsa, RSA_PKCS1_PADDING) != -1)
				{
					printf("\nMessage decrypted to : %s\n", decrypted);
				}
				else
				{
					printf("Badness has occured! decryption failed\n");
					return 1;
				}

				text = answer;
				return 0;
			}
		}

		int writeSSLSocket(int socket, string text, RSA* rsa)
		{
			cout << "In writeSSLSocket" << endl;
			unsigned char* encrypted = (unsigned char *) malloc(1000);
			int bufSize = RSA_private_encrypt(text.length(), (unsigned char *) text.c_str(), encrypted, rsa, RSA_PKCS1_PADDING);
			if (bufSize == -1)
			{
				printf("Badness has occured! encryption failed\n");
				return 0;
			}
			else
			{
				printf("Encrypted the message OK! = \n%s\n", encrypted );
			}

			if(write(socket,(char *)encrypted, bufSize) == -1)
			{
				return lcm::error::NET_COULD_NOT_WRITE;
			}
			else
			{
				return 0;
			}
		}
	} // end of namespace ssl
} //end of namespace lcm
